#ifdef _WIN32
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <MSWSock.h>
#else
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#endif // _WIN32
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <errno.h>

#include <chrono>
#include <string>
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <unordered_map>
#include <memory>

constexpr int MAX_LEN = 256;
constexpr int MAX_BUF = 1024;
#ifndef _WIN32
typedef int SOCKET;
#define CLOSESOCKET close
#else
#define CLOSESOCKET closesocket
#pragma comment(lib, "ws2_32.lib")
#endif // !_WIN32

void str_cli(SOCKET sockfd) {
    char sendbuf[MAX_BUF];
    char recvbuf[MAX_BUF];

    int bytes;

    while (fgets(sendbuf, MAX_BUF, stdin) != NULL) {
        bytes = (int)strlen(sendbuf);
        while (bytes > 0) {
            if (sendbuf[bytes - 1] != '\r' && sendbuf[bytes - 1] != '\n') {
                break;
            }
            bytes -= 1;
        }
        if (send(sockfd, sendbuf, bytes, 0) < 0) {
            printf("send error, errno:%d\n", errno);
            return;
        }
        bytes = recv(sockfd, recvbuf, MAX_BUF - 1, 0);
        if (bytes == 0) {
            printf("recv zero, connection may be closed.\n");
            return;
        }
        if (bytes < 0) {
            if (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN) {
                printf("recv error, skip ...\n");
            } else {
                printf("recv error, errno:%d\n", errno);
                return;
            }
        } else {
            recvbuf[bytes] = 0;
            fprintf(stdout, "recv:'%s'\n", recvbuf);
        }
    }
}

int main(int argc, char *argv[]) {
    struct sockaddr_in server_addr;
    int len, port, i, r;
    SOCKET sockfd;
    char *address;
    char ip_str[MAX_LEN];

    if (argc != 2) {
        printf("usage: client <ip:port>\n");
        return -1;
    }

#ifdef _WIN32
    WSADATA data;
    int status;
    status = WSAStartup(MAKEWORD(2, 0), &data);
    if (status != 0) {
        printf("failed to init win sock\n");
        return -1;
    }
#endif // _WIN32

    address = argv[1];
    len = (int)strlen(address);

    if (len >= MAX_LEN) {
        printf("usage: 'ip:port' is too long\n");
        return -1;
    }

    for (i = 0; i < len; i++) {
        if (address[i] == ':') {
            goto done;
        }
    }
    printf("usage: input param format error\n");
    return -1;
done:
    memcpy(ip_str, address, i);
    ip_str[i] = 0;

    port = atoi(&address[i + 1]);

    printf("prepare to connect server '%s:%d'\n", ip_str, port);

    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == (SOCKET)(-1)) {
        printf("failed to create socket\n");
        return -1;
    }

    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    if (inet_pton(AF_INET, ip_str, &server_addr.sin_addr) < 0) {
        printf("inet_pton error for: %s\n", ip_str);
        return -1;
    }

    r = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    printf("connect result: %d\n", r);

    str_cli(sockfd);
    CLOSESOCKET(sockfd);
#ifdef _WIN32
    WSACleanup();
#endif // _WIN32
    return 0;
}
